package com.example.micro.thread.counter;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 不使用ThreadLocal的另一个版本，创建一个subCounter用于每个线程计数。
 */
public class ThradeLocalCount3 {

    static class Counter {
        private static class Entry {
            long count = 0;
        }
 
        public interface SubCounter {
            void increase();
        }

        private static class SubCounterImpl implements SubCounter {
            private final Entry entry;
 
            SubCounterImpl(Entry entry) {
                this.entry = entry;
            }

            @Override
            public void increase() {
                entry.count++;
            }
        }
 
        private volatile long removedTotal = 0;
        long q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, qa, qb, qc, qd, qe;
 
        private final Lock lock = new ReentrantLock();
        private final Map<WeakReference<SubCounter>, Entry> map = new HashMap<>();
        private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
 
        public SubCounter createSubCounter() {
            expunge();
            Entry entry = new Entry();
            SubCounter subCounter = new SubCounterImpl(entry);
            lock.lock();
            try {
                map.put(new WeakReference<>(subCounter, queue), entry);
            } finally {
                lock.unlock();
            }
            return subCounter;
        }
 
        private void expunge() {
            for (Object x; (x = queue.poll()) != null; ) {
                synchronized (queue) {
                    lock.lock();
                    try {
                        Entry e = map.get(x);
                        map.remove(x);
                        removedTotal += e.count;
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }
 
        public long getAll() {
            expunge();
            return map.values().stream().map(entry -> entry.count).reduce(0L, Long::sum) + removedTotal;
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        int number = 4;
        Thread[] threads = new Thread[number];
        for (int i = 0; i < number; i++) {
            threads[i] = new Thread(new Runnable() {
                Counter.SubCounter subCounter = counter.createSubCounter();
                @Override
                public void run() {
                    for (int j = 0; j < 100_000_000; j++) {
                        subCounter.increase();
                    }
                }
            }
            );
        }
        for (Thread thread1 : threads) {
            thread1.start();
        }
        for (Thread thread2 : threads) {
            thread2.join();
        }
        for (int i = 0; i < number; i++) {
            threads[i] = new Thread(new Runnable() {
                Counter.SubCounter subCounter = counter.createSubCounter();
                @Override
                public void run() {
                    for (int j = 0; j < 100_000_000; j++) {
                        subCounter.increase();
                    }
                }
            }
            );
        }
        long start = System.currentTimeMillis();
        for (Thread thread1 : threads) {
            thread1.start();
        }
        for (Thread thread2 : threads) {
            thread2.join();
        }
        System.out.printf("Time:%d\n", System.currentTimeMillis() - start);
        System.out.printf("Count:%d\n", counter.getAll());
 
        start = System.currentTimeMillis();
        int intCount = 0;
        for (long i = 0; i < 800_000_000L; i++) {
            intCount++;
        }
        System.out.printf("Time:%d\n", System.currentTimeMillis() - start);
        System.out.printf("Count:%d\n", intCount);
 
        // 测试垃圾回收
        Counter counter2 = new Counter();
        start = System.currentTimeMillis();
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 200_000; j++) {
                counter2.createSubCounter().increase();
            }
        }
        System.out.printf("Time:%d\n", System.currentTimeMillis() - start);
        System.out.printf("Count:%d\n", counter2.getAll());
        System.out.printf("counter size:%d\n", counter.map.size());
        System.out.printf("counter2 size:%d\n", counter2.map.size());
    }
}